今天,我將介紹 contentChildren
,它是 @ContentChild
裝飾器 (decorator) 的訊號對應部分。
ContentChildren
裝飾器傳回 ReadT[]
,而 contentChildren
函數傳回 `Signal<readonly ReadT[]>。ContentChildren
裝飾器 (decorator) 的 static
屬性從 contentChild
函數中刪除。在下面的例子中,我展示了 contentChildren
如何透過範本變數 (template variables)、ngTemplates 和 Angular 組件查詢元素
<app-query-by-type>
<ng-template let-now><p>Custom Header 3, now = {{ now }}</p></ng-template>
<ng-template let-now>
<p>Custom Body 3</p>
<p>now = {{ now }}</p>
</ng-template>
</app-query-by-type>
templates = contentChildren(TemplateRef);
constructor() {
console.log('constructor', this.templates());
}
ngAfterContentInit(): void {
console.log('ngAfterContentInit', this.templates());
}
contentChildren
函數在 OnInit
hook執行後可用。 在 constructor 中,元素的數量為0。在ngOnInit
和 ngContentInit
中,TemplateRef
的數量為2。
在以下例子中,我展示了 viewChildren
如何透過範本變數 (template variables)、指令 (directives) 和 Angular 元素進行查詢。
import { Component, contentChild, effect, ElementRef, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-query-by-variable',
standalone: true,
imports: [FormsModule],
template: `
<div class="container">
<h3>Query ContentChildren by variables</h3>
<label for="color">
<span>Color: </span>
<select id="color" name="color" [(ngModel)]="color">
<option value="">----</option>
<option value="red">red</option>
<option value="yellow">yellow</option>
<option value="cyan">cyan</option>
<option value="goldenrod">goldenrod</option>
<option value="pink">pink</option>
</select>
</label>
<ng-content></ng-content>
</div>
`,
})
export class AppQueryByVariableComponent {
color = signal('');
}
AppQueryByVaraibleComponent
組件具有用於頁首、頁尾和正文的 <ng-content />
元素。 它還具有一個下拉式選單來選擇元素的背景顏色。
<app-query-by-variable>
<div #divs class="projection">Custom Header 2</div>
<div #divs class="projection">Custom Body</div>
<div #divs class="projection">
<ul>
<li>Custom Footer</li>
<li>Custom Footer 2</li>
</ul>
</div>
</app-query-by-variable>
在 App
組件中,div 元素 被投影到 <app-query-by-variable>
的預設 <ng-content>
。 div 元素具有相同的範本變數 #div
,我們可以使用 contentChildren
函數來查詢所有 <div>
元素。
divs = contentChildren('divs', { read: ElementRef });
contentChildren
函數透過範本變數 (template variables) 查詢元素。 第二個參數 { read: ElementRef }
期望每個項目都是 ElementRef
。
constructor() {
effect(() => {
this.divs().forEach((div) =>
div.nativeElement.style.backgroundColor = this.color()
);
});
}
當 color
signal 更新時,effect
會執行邏輯來變更背景顏色的 CSS 樣式。 contentChildren
函數傳回一個 ElementRef
array,它可以是一個 empty array。迭代 (iterate) 該 array 以更新 div 元素的背景顏色。
@Directive({
selector: '[someDirective]',
standalone: true,
})
export class AppSomeDirective {
templates = contentChildren(TemplateRef);
}
我們可以寫一個 AppSomeDirective
指令 (directive),使用 contentChildren
來查詢投影的 ngTemplates。
@Component({
selector: 'app-query-by-type',
standalone: true,
imports: [AppSomeDirective, NgTemplateOutlet, FormsModule],
template: `
<div class="container">
<h3>Query ContentChildren by TemplateRef</h3>
<div someDirective>
<ng-template>
<p>Projected item</p>
<p>Projected item 2</p>
<p>Projected Item 3</p>
</ng-template>
<ng-template>
<p>Template 2</p>
</ng-template>
</div>
@for (template of directive().templates(); track $index) {
<ng-container *ngTemplateOutlet="template"></ng-container>
}
<label for="index">
<span>Template: </span>
<select id="index" name="index" [(ngModel)]="index" style="margin-right: 0.25rem;">
<option value="">----</option>
@for (x of templates(); track $index) {
<option [value]="$index">{{ $index + 1 }}</option>
}
</select>
</label>
<button (click)="changeTemplate()">Change template</button>
<ng-container *ngTemplateOutlet="dynamicTemplate() || defaultTemplate"></ng-container>
<ng-template #defaultTemplate>
<p>Choose a template from the dropdown.</p>
</ng-template>
</div>
`,
})
export class AppQueryByDirectiveComponent {
index = signal('');
directive = viewChild.required(AppSomeDirective);
}
div 元素有一個 someDirective
屬性;因此,指令 (directive) 的 contentChildren
可以檢索兩個 ngTemplate。此組件也可以查詢投影在 <app-query-by-type>
標記內的 ngTemplates。
@for (template of directive().templates(); track $index) {
<ng-container *ngTemplateOutlet="template">
</ng-container>
}
AppQueryByDirectiveComponent
組件使用 viewChild
函數來查詢指令 (directive) 並存取其 contentChildren
。該指令的 ngTemplates 被指派給 ngTemplateOutlet 指令來渲染動態內容。
<label for="index">
<span>Template: </span>
<select id="index" name="index" [(ngModel)]="index" style="margin-right: 0.25rem;">
<option value="">----</option>
@for (x of templates(); track $index) {
<option [value]="$index">{{ $index + 1 }}</option>
}
</select>
</label>
<button (click)="changeTemplate()">Change template</button>
<ng-container *ngTemplateOutlet="dynamicTemplate() || defaultTemplate"></ng-container>
<ng-template #defaultTemplate>
<p>Choose a template from the dropdown.</p>
</ng-template>
export class AppQueryByDirectiveComponent {
index = signal('');
templates = contentChildren(TemplateRef);
dynamicTemplate = signal<TemplateRef<any> | undefined>(undefined);
changeTemplate() {
const idx = this.index();
const t = idx ? this.templates()[+idx] : undefined;
this.dynamicTemplate.set(t);
}
}
AppQueryByDirectiveComponent
組件使用 contentChildren
函數來查詢預設 <ng-content>
中存在的 TemplateRef
。當使用者按一下該按鈕時,將執行 changeTemplate
以確定要顯示的範本。 當所選索引為空字串時,將顯示預設範本。否則,組件將顯示 <app-query-by-type>
標記中投影的範本。
<app-query-by-type>
<ng-template><p>Custom Header 3</p></ng-template>
<ng-template><p>Custom Body 3</p></ng-template>
</app-query-by-type>
在 App
組件中,ngTemplates 被投影到 <app-query-by-type>
中,以便 contentChildren
函數可以查詢它們並將它們指派給 templates
signal。
import { Component, ChangeDetectionStrategy, signal, computed } from '@angular/core';
const imgURL = 'https://picsum.photos/300/200';
@Component({
selector: 'app-photo',
standalone: true,
template: `
<div class="photo">
<img [src]="img()" alt="Random picture" />
</div>
`,
})
export default class AppPhotoComponent {
#random = signal(Date.now());
img = computed(() => `${imgURL}?random=${this.#random()}`)
loadImage() {
this.#random.set(Date.now());
}
}
AppPhotoComponent
組件具有附加到圖像 URL 的random seed signal。 當 loadImage
函數更新signal value 時,img
computed signal會產生一個新的圖片 URL。
import { Component, ChangeDetectionStrategy, contentChildren, signal } from '@angular/core';
import AppPhotoComponent from './photo.component';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-photo-wrapper',
standalone: true,
imports: [FormsModule],
template: `
<div class="photo-wrapper">
<div class="photos">
<ng-content />
</div>
<div>
<label for="index">
<span>Photo: </span>
<select id="index" name="index" [(ngModel)]="index">
<option value="">----</option>
@for (x of photos(); track $index) {
<option [value]="$index">{{ $index + 1 }}</option>
}
</select>
</label>
<button (click)="changeImage()">Change photo</button>
</div>
</div>
`,
})
export default class AppPhotoWrapperComponent {
index = signal('');
photos = contentChildren(AppPhotoComponent);
changeImage() {
const strIdx = this.index();
if (strIdx) {
const index = +this.index();
this.photos()[index].loadImage();
}
}
}
AppPhotoWrapperComponent
組件包含一個預設的 <ng-content>
,我們可以用它來投影 AppPhotoComponent
組件。此組件使用 contentChidren
函數來查詢所有 AppPhotoComponent
組件。下拉清單使用 ngModel
將值綁定到 index
signal。當使用者點擊該按鈕時,changeImage
方法將呼叫 loadImage
方法來顯示新圖片。
<app-photo-wrapper>
<app-photo class="photo" />
<app-photo class="photo" />
<app-photo class="photo" />
</app-photo-wrapper>
在 <app-photo-wrapper>
tag 有三個 <app-photo>
tags,AppPhotoComponent
組件被投影到<ng-content>
。 contentChildren
函式成功查詢 AppPhotoWrapperComponent
中的 AppPhotoComponent
。
contentChildren
可以查詢元素、TemplateRef 和組件。第一個參數是一個 selector,它是範本變數 (template variables) 或類型。read
屬性指定 contentChildren
傳回的元素類型。contentChildren
函數傳回 signal
中的元素 array。 當函數找不到匹配項時,它會傳回一個 empty array,而不是錯誤。contentChildren
在 OnInit hook 後可用。鐵人賽的第 22 天就這樣結束了。